#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <pvm3.h>
#include <sys/time.h>
#include <math.h>


void configure_parallel_program(int argc, char *argv[], int *nproc, int *mytid, int **tids, int *ptid, double *niter, int *method, int *root);
void dowork( int ptid, int mytid, int *tids, int nproc , double niter, int method, int root);
void enviar_double(double numero, int mytid,int tid, int msgtag);
void enviar_double_mcast(double numero, int mytid,int *tids,int ntasks, int msgtag);
void recibir_double(double *numero, int mytid, int tid, int msgtag);
void enviar_int(int numero, int mytid,int tid, int msgtag);
void enviar_int_mcast(int numero, int mytid,int *tids, int ntasks,int msgtag);
void recibir_int(int *numero, int mytid, int tid, int msgtag);
void error(char * error);
double montecarlo(double niter,int seed);
void mostrar_resultado(double pi);


int main( int argc, char *argv[] )
{
    int nproc = 0;             /* numero de procesos, se le pone un valor porque la funcion dowork requiere nproc */
    int mytid;                 /* id del proceso */
    int *tids;                 /* array con las id de los procesos */
    int ptid;                  /* id del proceso padre */
    double niter;              /* numero de operaciones */  		
    int method;                /* metodo de uso: master/slave(0) spmd(1) */
    int root;                  /* proceso root del grupo */
	
    configure_parallel_program(argc,argv,&nproc,&mytid,&tids,&ptid, &niter, &method, &root);
	 		
    dowork( ptid, mytid, tids, nproc ,niter, method, root);

    /* al salir del programa salimos de pvm */
    if( pvm_exit() < 0 ) {
		pvm_perror ("ERROR main(): pvm_exit \n");
    }

    exit(1);
}

/* muestra un error y finaliza el programa */
void error(char * error){            
      pvm_perror(error);
      pvm_exit();
      exit(0);    
}

/* envia un double de un proceso a otro */
void enviar_double(double numero, int mytid,int tid, int msgtag){

    int count  = 1;
    int stride = 1;

    
    if( pvm_initsend( PvmDataDefault ) < 0 )
        error("ERROR enviar_double(): pvm_initsend \n");
    if( pvm_pkdouble( &numero, count, stride ) < 0 ) 
        error ("ERROR enviar_double(): pvm_pkdouble \n");
    if( pvm_send( tid, msgtag) < 0 ) 
        error ("ERROR enviar_double(): pvm_send \n");
      
    printf("%d:DOUBLE message token='%f' sent to %d\n",mytid,numero,tid);
}

/* envia un double a varios procesos */
void enviar_double_mcast(double numero, int mytid,int *tids,int ntasks, int msgtag){
	
	int count  = 1;
	int stride = 1;

    
    if( pvm_initsend( PvmDataDefault ) < 0 ) 
        error ("ERROR enviar_double_mcast(): pvm_initsend \n");
    if( pvm_pkdouble( &numero, count, stride ) < 0 ) 
        error ("ERROR enviar_double_mcast(): pvm_pkdouble \n");     
    if( pvm_mcast(tids, ntasks, msgtag) < 0 ) 
        error ("ERROR enviar_double_mcast(): pvm_mcast \n");

    printf("%d:DOUBLE message token='%f' sent to MULTIPLE TIDS\n",mytid,numero);
}



/* recibe un double desde un proceso a otro */
void recibir_double(double *numero, int mytid, int tid, int msgtag){

	int count  = 1;
	int stride = 1;	
	
    if( pvm_recv( tid, msgtag ) < 0 ) 
        error ("ERROR recibir_double(): pvm_recv \n");
    if( pvm_upkdouble(numero, count, stride ) < 0 ) 
        error ("ERROR recibir_double(): pvm_upkdouble \n");

    printf("%d:DOUBLE message token='%f' received from %d\n",mytid,*numero,tid);
}


/* envia un int de un proceso a otro */
void enviar_int(int numero, int mytid,int tid, int msgtag){
	
	int count  = 1;
	int stride = 1;
    
    if( pvm_initsend( PvmDataDefault ) < 0 ) 
        error ("ERROR enviar_int(): pvm_initsend \n");
    if( pvm_pkint( &numero, count, stride ) < 0 ) 
        error ("ERROR enviar_int(): pvm_pkdouble \n");
    if( pvm_send( tid, msgtag) < 0 ) 
        error ("ERROR enviar_int(): pvm_send \n");

    printf("%d:INT message token='%d' sent to %d\n",mytid,numero,tid);
}


/* envia un int a varios procesos */
void enviar_int_mcast(int numero, int mytid,int *tids, int ntasks,int msgtag){
	
	int count  = 1;
	int stride = 1;

    
    if( pvm_initsend( PvmDataDefault ) < 0 ) 
        error ("ERROR enviar_int_mcast(): pvm_initsend \n");
    if( pvm_pkint( &numero, count, stride ) < 0 ) 
        error ("ERROR enviar_int_mcast(): pvm_pkdouble \n");
    if( pvm_mcast(tids, ntasks, msgtag) < 0 ) 
        error ("ERROR enviar_int_mcast(): pvm_mcast \n");

    printf("%d:INT message token='%d' sent to MULTIPLE TIDS\n",mytid,numero);
}


/* recibe un int de otro proceso */
void recibir_int(int *numero, int mytid, int tid, int msgtag){

	int count  = 1;
	int stride = 1;	
	
    if( pvm_recv( tid, msgtag ) < 0 ) 
        error ("ERROR recibir_int(): pvm_recv \n");
    if( pvm_upkint(numero, count, stride ) < 0 ) 
        error ("ERROR recibir_int(): pvm_upkdouble \n");

    printf("%d:INT message token='%d' received from %d\n",mytid,*numero,tid);
}


/*  En esta funcion se configuran los parametros del programa tanto del padre como de los procesos que genera */
void configure_parallel_program(int argc, char *argv[], int *nproc, int *mytid, int **tids, int *ptid, double *niter, int *method, int *root)
{
    int i, cc;
	
    (*mytid) = pvm_mytid();
	
    (*ptid) = pvm_parent();
	
    if( *ptid < 0 )       /* proceso padre */
    {
		
		printf("%d: Master process\n",(*mytid));
		/* Lecuta de informacion de la linea de comandos */
		if (argc != 4) 
			error("ERROR configure_parallel_program(): Command line of the master process \n");		
		
		(*method) = atoi(argv[1]);//METODO A USAR
	    printf("%d: Master process parsed the command line: nproc = %d\n",(*mytid),(*nproc));
			
			
		if(((*nproc) = atoi(argv[2])) <= 0)//NUMERO DE PROCESOS
		    error("ERROR configure_parallel_program(): Number of process must be positive\n");		    	    
		if(!(*method))    //En Maestro Esclavo el numero de procesos a crear es uno a mayores ya que el padre no realiza trabajo
		    (*nproc)++;			
								
		if( ( (*tids)=(int *)malloc((*nproc)*sizeof(int)) ) == NULL ) 
			error ("ERROR configure_parallel_program(): Not enough memory in the master process \n");			
    		
    		
		(*niter) = (double)atoll(argv[3]);//NUMERO DE ITERACIONES		
		if (*nproc > *niter) 
		    error ("ERROR configure_parallel_program(): Number of process must be less than number of iterations \n");



		
		(*tids)[0] = (*mytid);		
		/* crea procesos hijo */
		if (!(((*nproc) == 1) && (*method)))   // EXCEPCION spmd y con un solo proceso
		    if( (cc=pvm_spawn("p1", (char**)0, 0, "", (*nproc)-1, &(*tids)[1])) < (*nproc)-1 ) 
			    error ("ERROR configure_parallel_program(): Not enough processes created \n");




        /* envio del metodo o los procesos creados */
        for( i=1; i<*nproc; i++ ) {	
                enviar_int(*method,*mytid,(*tids)[i], 1);
	    }

        /* Envio de numero de iteraciones a los hijos */		
        if(!(*method)) {  // M&S
	        for( i=1; i<*nproc; i++ ) {		
                enviar_double(*niter/(double)((*nproc)-1),*mytid,(*tids)[i], 2);
	        }	
        }	
        else              // SPMD
        {
            enviar_double_multiple(*niter/(double)((*nproc)), *mytid, (*tids)+1, (*nproc)-1, 2); //envio de num iteraciones
            *niter = *niter/(double)((*nproc));            
            enviar_int_multiple(*nproc, *mytid, (*tids)+1, (*nproc)-1, 3); //envio de num procesos            
            
            if(*root = pvm_joingroup("grupo1") < 0)
		        error("ERROR configure_parallel_program(): pmv_joingroup \n");

            enviar_int_multiple(*root, *mytid, (*tids)+1, (*nproc)-1, 4); //envio de inum                   
        }
    	    
		
		
    }
    else    /* proceos hijo */
    {
		printf("%d: Slave process\n",(*mytid));
		
		if (argc != 1) 
			error("ERROR configure_parallel_program(): Command line of the slave process \n");
		
		recibir_int(method, *mytid, *ptid, 1);			

        recibir_double(niter, *mytid, *ptid, 2);
        

        if (*method == 1){ // SPMD
            recibir_int(nproc, *mytid, *ptid, 3);
            if(pvm_joingroup("grupo1") < 0)
    			error("ERROR configure_parallel_program(): pmv_joingroup \n");
       
            recibir_int(root, *mytid, *ptid, 4);            
        }	  	
		
	}
}


/* calcula el numero de veces que un punto cae dentro de una circunferencia de radio 1
   segun el numero de iteraciones que se le pase. 
   Recibe una semilla para crear el random que necesita */
double montecarlo(double niter,int seed){
    
    double i,x,y,enCirculo;

    srandom(seed);
    for (i=1; i<=niter; i++) {
          
          x = (double) random() / (double) RAND_MAX;
          y = (double) random() / (double) RAND_MAX;
          
          if ((x*x + y*y) <= 1.0) enCirculo++;
    }
    
    return enCirculo;
}


void mostrar_resultado(double pi){
 	printf ("Valor de PI = %.6lf\n", pi);
    printf ("Error       = %.6lf\n", fabs(pi-M_PI));	
}

/* En esta funcion se realiza el calculo del numero de montecarlo y se calcula pi a partir de el */
void dowork( int ptid, int mytid, int *tids, int nproc, double niter, int method, int root)
{
	double i;
	double valor;
	double resultado = 0;
	int msgtag = 4;
	
	double enCirculo=0;
	double pi;		

    if(!method) //M&S
	    if( ptid < 0 )  // proceso padre
	    {
            for( i=1; i<nproc; i++ ) {                  
                  recibir_double(&valor, mytid, -1, msgtag);
                  resultado+=valor;                
            }		
            pi = (4.0 * resultado) / niter;		
            mostrar_resultado(pi);
	    }
	    else    // procesos hijo
	    {
            enCirculo = montecarlo(niter, mytid);
            enviar_double(enCirculo, mytid, ptid, msgtag);				
	    }
    else      //SPMD (tanto el padre como los hijos realizan el calculo)
    {    
        enCirculo = montecarlo(niter, mytid);
        
        if(pvm_barrier("grupo1", (nproc)) < 0)
			error("ERROR do_work(): pvm_barrier \n");         
			
        if(pvm_reduce(PvmSum, &enCirculo, 1, PVM_DOUBLE, 5, "grupo1", root) < 0)
			error("ERROR do_work(): pvm_reduce \n");  
			      
        if(pvm_barrier("grupo1", (nproc)) < 0)
			error("ERROR do_work(): pvm_barrier \n");

        if(pvm_lvgroup("grupo1") < 0)
			error("ERROR do_work(): pvm_lvgroup \n");
       
        if( ptid < 0 )  // proceso padre
	    {
            pi = (4.0 * enCirculo) / (niter * nproc);			    
            mostrar_resultado(pi);
        }	        
            
    }        
}
